#!/usr/bin/python -S
"""
echo.py

Usage:

  tnet echo { \
    string = .foo \
    bytes = /%00%00  # 2 NUL bytes
    int = 1
    float = 1.3e10
    bool = true
    null = null
    list = [ 1 2 3 false .str /bytes ]
  }

In bash, it appears that { and } don't need to be escaped as command arguments.

NOTES
  - No commas in syntax.
  - Strings must be utf-8 encoded

Flags:

  byte-escape char?  Nah probably just choose %.
"""

__author__ = 'Andy Chu'


import sys
import urllib


class Error(Exception):
  pass


# test for identity with 'is'
OPEN_BRACE = {}
CLOSE_BRACE = {}

OPEN_BRACKET = {}
CLOSE_BRACKET = {}


CONSTANTS = {
    'true': True,
    'false': False,
    'null': None,
    '{': OPEN_BRACE,
    '}': CLOSE_BRACE,
    '[': OPEN_BRACKET,
    ']': CLOSE_BRACKET,
    }


def ParseToken(t):
  # consider accepting $ since that's the tag?  awkward in bash.
  if t.startswith('.'):
    return unicode(t[1:], 'utf-8')

  # consider accepting , since that's the tag?  too similar to '.'?
  if t.startswith('/'):
    return urllib.unquote(t[1:])

  try:
    return CONSTANTS[t]
  except KeyError:
    pass

  try:
    return int(t)
  except ValueError:
    pass

  try:
    return float(t)
  except ValueError:
    pass

  raise Error('Invalid token %r' % t)


def MakeList(tokens):
  items = []
  while True:
    try:
      obj = _Parse(tokens)
    except StopIteration:
      raise Error("Incomplete list (so far: %s)" % items)

    if obj is CLOSE_BRACKET:
      return items
    items.append(obj)


def MakeDict(tokens):
  d = {}
  while True:
    try:
      k = _Parse(tokens)
    except StopIteration:
      raise Error("Incomplete dictionary (looking for key, so far: %s)" % d)

    if k is CLOSE_BRACE:
      return d

    try:
      v = _Parse(tokens)
    except StopIteration:
      raise Error("Incomplete dictionary (looking for value, so far: %s)" % d)

    if v is CLOSE_BRACE:
      raise Error('Got close brace after key %r' % k)

    d[k] = v


def _Parse(tokens):
  """Parse a single tnet value.

  Args:
    tokens: an iterator that yields strings.
  """
  t = tokens.next()  # raises StopIteration
  token = ParseToken(t)

  if token is OPEN_BRACKET:
    return MakeList(tokens)
  elif token is CLOSE_BRACKET:
    return CLOSE_BRACKET

  if token is OPEN_BRACE:
    return MakeDict(tokens)
  elif token is CLOSE_BRACE:
    return CLOSE_BRACE

  else:
    return token


def Parse(tokens):
  """Parse multiple tnet values.

  Given a list of tokens, returns a list of values.  [ 1 ] { .a .b } is a list
  and a dictionary side by side.
  """
  t = iter(tokens)  # list to iterator
  values = []
  while True:
    try:
      values.append(_Parse(t))
    except StopIteration:
      break
    except Error, e:  # e.g. invalid token
      print >>sys.stderr, e,
      sys.exit(1)
  return values
